package main

// #cgo CFLAGS: -I/opt/homebrew/include -I/opt/homebrew/opt/libffi/include
// #cgo LDFLAGS: -L/opt/homebrew/lib -lpcre2-8 -L/opt/homebrew/opt/libffi/lib -lffi
/*
#include "ffi.h"
#include "mypcre2.h"
#include <stdlib.h>
#include <string.h>
typedef void (*_fnptr_t)(void);
typedef void** argumentsPtr;
typedef void* _pointer;

const int ptrSize = sizeof(uintptr_t);

static void **argsArrayNew(int nargs) {
	void** ptr = (void **)(malloc(nargs * ptrSize));
	memset(ptr, 0, nargs * ptrSize);
	return ptr;
}

static void argsArraySet(void **args, int index, void *ptr) {
	args[index] = ptr;
}

static void argsArrayFree(void **args) {
	free(args);
}

static void ffi_call_preg_match(ffi_cif *cif, void *rvalue, void **values) {
	ffi_call(cif, preg_match, rvalue, values);
}

static int add(int a, int b) {
    return a + b;
}

static void ffi_call_add(ffi_cif *cif, void *rvalue, void **values) {
	ffi_call(cif, add, rvalue, values);
}

static void ffi_call_strlen(ffi_cif *cif, void *rvalue, void **values) {
	ffi_call(cif, strlen, rvalue, values);
}
*/
import "C"

import (
	"fmt"
	goffi "github.com/clevabit/libgoffi"
	"reflect"
	"unsafe"
)

// ffi_call_preg_match_static -> |C代码| ffi_call_preg_match-> ffi_call(preg_match)
func ffi_call_preg_match_static(pattern, subject string) (results []string) {
	ffi := NewPcre2CgoFfi()
	results, _ = ffi.CallPregMatchStatic(pattern, subject)
	return results
}

// ffi_call_cgo_preg_match -> C.ffi_call(C.preg_match)
func ffi_call_cgo_preg_match(pattern, subject string) (results []string) {
	ffi := NewPcre2CgoFfi()
	results, _ = ffi.CallCgoPregMatch(pattern, subject)
	return
}

// ffi_call_preg_match_dylibmypcre2  -> C.ffi_call(fn from dlsym)
func ffi_call_preg_match_dylibmypcre2(pattern, subject string) (results []string) {
	ffi := NewPcre2CgoFfi()
	results, _ = ffi.CallDylib("preg_match", pattern, subject)
	return
}

type CgoFfi struct {
	ffi_cif  C.ffi_cif
	ffi_ret  *C.ffi_type
	ffi_args **C.ffi_type
}

func NewPcre2CgoFfi() *CgoFfi {
	//int preg_match(char *pattern, char *subject, char*** matches);
	ptrSize := int(C.ptrSize)
	ffi := new(CgoFfi)
	var argc = 3
	ffi.ffi_args = (**C.ffi_type)(C.malloc(C.size_t(ptrSize * argc)))
	header := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ffi.ffi_args)), Len: argc, Cap: argc}
	args := *(*[]*C.ffi_type)(unsafe.Pointer(&header))
	args[0] = &C.ffi_type_pointer
	args[1] = &C.ffi_type_pointer
	args[2] = &C.ffi_type_pointer
	ffi.ffi_ret = &C.ffi_type_sint32

	if status := int(C.ffi_prep_cif(&ffi.ffi_cif, C.FFI_DEFAULT_ABI, C.uint(argc), ffi.ffi_ret, ffi.ffi_args)); status != int(C.FFI_OK) {
		panic(status)
	}
	return ffi
}

func (ffi *CgoFfi) CallPregMatchStatic(pattern, subject string) (results []string, err error) {
	//var matches **C.char
	ptrSize := int(C.ptrSize)
	matches := (**C.char)(C.malloc(C.size_t(ptrSize)))
	var rc C.int
	argsv := C.argsArrayNew(C.int(3))
	cs_pattern := C.CString(pattern)
	cs_subject := C.CString(subject)
	C.argsArraySet(argsv, C.int(0), unsafe.Pointer(&cs_pattern))
	C.argsArraySet(argsv, C.int(1), unsafe.Pointer(&cs_subject))
	C.argsArraySet(argsv, C.int(2), unsafe.Pointer(&matches))

	C.ffi_call_preg_match(&ffi.ffi_cif, unsafe.Pointer(&rc), argsv)

	defer C.argsArrayFree(argsv)
	defer C.free(unsafe.Pointer(matches))

	var pbuf []*C.char
	header := (*reflect.SliceHeader)(unsafe.Pointer(&pbuf))
	header.Cap = int(rc)
	header.Len = int(rc)
	header.Data = uintptr(unsafe.Pointer(*matches))
	for _, i := range pbuf {
		results = append(results, C.GoString(i))
	}

	return
}

func (ffi *CgoFfi) CallCgoPregMatch(pattern, subject string) (results []string, err error) {
	var ret C.int
	cs_pattern := C.CString(pattern)
	cs_subject := C.CString(subject)
	//var matches **C.char
	ptrSize := int(C.ptrSize)
	matches := (**C.char)(C.malloc(C.size_t(ptrSize)))
	defer C.free(unsafe.Pointer(matches))
	ffi.callCgoPregMatch(unsafe.Pointer(&ret), unsafe.Pointer(&cs_pattern), unsafe.Pointer(&cs_subject), unsafe.Pointer(&matches))

	var pbuf []*C.char
	header := (*reflect.SliceHeader)(unsafe.Pointer(&pbuf))
	header.Cap = int(ret)
	header.Len = int(ret)
	header.Data = uintptr(unsafe.Pointer(*matches))
	for _, i := range pbuf {
		results = append(results, C.GoString(i))
	}
	return
}

func (ffi *CgoFfi) callCgoPregMatch(ret unsafe.Pointer, pattern, subject unsafe.Pointer, matches unsafe.Pointer) (err error) {
	args := C.argsArrayNew(C.int(3))
	C.argsArraySet(args, C.int(0), pattern)
	C.argsArraySet(args, C.int(1), subject)
	C.argsArraySet(args, C.int(2), matches)

	//C.preg_match 是cgo函数, 等价c函数
	_, err = C.ffi_call(&ffi.ffi_cif, C._fnptr_t(C.preg_match), ret, args)

	C.argsArrayFree(args)
	return
}

func (ffi *CgoFfi) CallDylib(sym, pattern, subject string) (results []string, err error) {
	var ret C.int
	cs_pattern := C.CString(pattern)
	cs_subject := C.CString(subject)
	//var matches **C.char
	ptrSize := int(C.ptrSize)
	matches := (**C.char)(C.malloc(C.size_t(ptrSize)))
	lib, err := dlopen("./libmypcre2.dylib")
	cfun, err := dlsym(lib, sym)
	ffi.callDylib(cfun, unsafe.Pointer(&ret), unsafe.Pointer(&cs_pattern), unsafe.Pointer(&cs_subject), unsafe.Pointer(&matches))

	var pbuf []*C.char
	header := (*reflect.SliceHeader)(unsafe.Pointer(&pbuf))
	header.Cap = int(ret)
	header.Len = int(ret)
	header.Data = uintptr(unsafe.Pointer(*matches))
	for _, i := range pbuf {
		results = append(results, C.GoString(i))
	}
	return
}

func (ffi *CgoFfi) callDylib(cfun unsafe.Pointer, ret unsafe.Pointer, pattern, subject unsafe.Pointer, matches unsafe.Pointer) (err error) {
	args := C.argsArrayNew(C.int(3))
	C.argsArraySet(args, C.int(0), pattern)
	C.argsArraySet(args, C.int(1), subject)
	C.argsArraySet(args, C.int(2), matches)
	
	_, err = C.ffi_call(&ffi.ffi_cif, C._fnptr_t(cfun), ret, args)

	C.argsArrayFree(args)
	return
}

func cgo_ffi_add_static(a, b int) int {
	ptrSize := int(C.ptrSize)
	var cif C.ffi_cif
	var argc = 2
	ffi_args := (**C.ffi_type)(C.malloc(C.size_t(ptrSize * argc)))
	header := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ffi_args)), Len: argc, Cap: argc}
	args := *(*[]*C.ffi_type)(unsafe.Pointer(&header))
	args[0] = &C.ffi_type_sint32
	args[1] = &C.ffi_type_sint32

	ffi_ret := &C.ffi_type_sint32

	if status := int(C.ffi_prep_cif(&cif, C.FFI_DEFAULT_ABI, C.uint(argc), ffi_ret, ffi_args)); status != int(C.FFI_OK) {
		panic(status)
	}

	var cargs C.argumentsPtr = C.argsArrayNew(C.int(2))
	var arga = C.int32_t(a)
	var argb = C.int32_t(b)
	C.argsArraySet(cargs, C.int(0), unsafe.Pointer(&arga))
	C.argsArraySet(cargs, C.int(1), unsafe.Pointer(&argb))
	var ret C.int32_t
	C.ffi_call_add(&cif, unsafe.Pointer(&ret), cargs)

	C.argsArrayFree(cargs)

	return int(ret)
}

func cgo_ffi_strlen_static(str string) int {
	ptrSize := int(C.ptrSize)
	var cif C.ffi_cif
	var argc = 1
	ffi_args := (**C.ffi_type)(C.malloc(C.size_t(ptrSize * argc)))
	header := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ffi_args)), Len: argc, Cap: argc}
	args := *(*[]*C.ffi_type)(unsafe.Pointer(&header))
	args[0] = &C.ffi_type_pointer
	ffi_ret := &C.ffi_type_sint32

	if status := int(C.ffi_prep_cif(&cif, C.FFI_DEFAULT_ABI, C.uint(argc), ffi_ret, ffi_args)); status != int(C.FFI_OK) {
		panic(status)
	}

	var cargs C.argumentsPtr = C.argsArrayNew(C.int(1))
	var arg = C.CString(str)
	C.argsArraySet(cargs, C.int(0), unsafe.Pointer(&arg))
	var ret C.int32_t
	C.ffi_call_strlen(&cif, unsafe.Pointer(&ret), cargs)
	C.argsArrayFree(cargs)

	C.ffi_call_strlen(&cif, unsafe.Pointer(&ret), cargs)

	return int(ret)
}

func cgo_ffi_std_dylib(a, b float64) float64 {
	ptrSize := int(C.ptrSize)
	var cif C.ffi_cif
	var argc = 2
	ffi_args := (**C.ffi_type)(C.malloc(C.size_t(ptrSize * argc)))
	header := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ffi_args)), Len: argc, Cap: argc}
	args := *(*[]*C.ffi_type)(unsafe.Pointer(&header))
	args[0] = &C.ffi_type_double
	args[1] = &C.ffi_type_double

	ffi_ret := &C.ffi_type_double

	if status := int(C.ffi_prep_cif(&cif, C.FFI_DEFAULT_ABI, C.uint(argc), ffi_ret, ffi_args)); status != int(C.FFI_OK) {
		panic(status)
	}

	lib, _ := dlopen("/Library/Developer/CommandLineTools/usr/lib/libclang.dylib")
	cfun, _ := dlsym(lib, "pow")

	var cargs C.argumentsPtr = C.argsArrayNew(C.int(2))
	var arga = C.double(a)
	var argb = C.double(b)
	C.argsArraySet(cargs, C.int(0), unsafe.Pointer(&arga))
	C.argsArraySet(cargs, C.int(1), unsafe.Pointer(&argb))
	var ret C.double
	C.ffi_call(&cif, C._fnptr_t(cfun), unsafe.Pointer(&ret), cargs)

	return float64(ret)
}

func libgoffi_test() {
	lib, err := goffi.NewLibrary("libclang", goffi.BindNow|goffi.BindGlobal)
	if err != nil {
		panic(err)
	}
	type fnPow = func(float64, float64) float64
	var pow fnPow
	err = lib.Import("pow", &pow)
	if err != nil {
		panic(err)
	}

	println(fmt.Sprintf("pow(2., 3.): %f", pow(2., 3.)))
}

func cgo_ffi_add_dylibfoo(a, b int) int {
	//问题一 ffi_args 引用go内存
	//问题二 libfoo.dylib找不到add
	var cif C.ffi_cif
	var argc = 2
	args := []*C.ffi_type{
		&C.ffi_type_sint32,
		&C.ffi_type_sint32,
	}
	ffi_args := &args[0] // &reflect.SliceHeader{Data}
	ffi_ret := &C.ffi_type_sint32

	if status := int(C.ffi_prep_cif(&cif, C.FFI_DEFAULT_ABI, C.uint(argc), ffi_ret, ffi_args)); status != int(C.FFI_OK) {
		panic(status)
	}

	lib, _ := dlopen("./libfoo.dylib")
	cfun, _ := dlsym(lib, "add")

	var cargs C.argumentsPtr = C.argsArrayNew(C.int(2))
	var arga = C.int32_t(a)
	var argb = C.int32_t(b)
	C.argsArraySet(cargs, C.int(0), unsafe.Pointer(&arga))
	C.argsArraySet(cargs, C.int(1), unsafe.Pointer(&argb))
	var ret C.int32_t
	C.ffi_call(&cif, C._fnptr_t(cfun), unsafe.Pointer(&ret), cargs)

	return int(ret)
}

// gcc -o libmypcre2.dylib -fpic -shared mypcre2.c -I/opt/homebrew/include -L/opt/homebrew/lib -lpcre2-8

// panic: runtime error: cgo argument has Go pointer to Go pointer
// ffi_create的ffi_args只能使用C.malloc创建出的内存,
// ffi_call的指针参数也需要使用C.malloc创建出的内存,
